package yagwl.service.util.svc;

import java.util.HashMap;
import java.util.logging.Logger;

import yagwl.service.util.CryptoUtil;
import yagwl.service.util.db.DaoContext;

public class Context {
  private static final Logger log = Logger.getLogger(Context.class.getName());
  private static final String Assign = "=";
  private static final String Delim = ":";
  private boolean offline;
  //private boolean simulated;
  //private boolean impersonated;
  //private String module;
  //private String testStatus;
  private boolean sessionUpdated = false;
  private DaoContext daoContext;
  private HashMap<String, Object> attrMap = new HashMap<String, Object>();
  private enum SessionAttr {
    sessionId, userId, orgId, tenantId
  }
  public enum Attr {
    Email, Nickname
  }

  private Context() {
    setCurrentInstance(this);
  }

  /**
   * initializes context given the session token of an ongoing session; if
   * session token is null, an anonymous user context is initialized
   */
  public Context(String sessionToken) {
    this();
    log.finer(sessionToken);
    if (sessionToken == null) {
      createAnonymousUserContext();
      return;
    }
    // // sessionToken = URLDecoder.decode(sessionToken);
    sessionToken = CryptoUtil.decryptSessionToken(sessionToken);
    log.info("Session Token: " + sessionToken);
    if (sessionToken == null) {
      log.info("Could not create context with given session token. "
          + "creating anonymous user context");
      createAnonymousUserContext();
      return;
    }

    String[] tuples = sessionToken.split(Delim);
    for (String tuple : tuples) {
      String[] nv = tuple.split(Assign);
      attrMap.put(nv[0], nv[1]);
    }
  }

  private void createAnonymousUserContext() {
    String sessionId = new Long(System.currentTimeMillis()).toString();
    putAttribute(SessionAttr.sessionId.toString(), sessionId);
    // TODO P3 ensure that the session id is unique
  }

  public String getSessionToken() {
    StringBuffer sb = new StringBuffer();
    for (SessionAttr attr : SessionAttr.values()) {
      if (attrMap.containsKey(attr.toString())) {
        if (sb.length() > 0)
          sb.append(Delim);
        sb.append(attr.toString() + Assign + attrMap.get(attr.toString()));
      }
    }
    log.info("Session token: " + sb.toString());
    String sessionToken = sb.toString();
    sessionToken = CryptoUtil.encryptSessionToken(sb.toString());
    // sessionToken = URLEncoder.encode(sessionToken);
    return sessionToken;
  }

  /**
   * initializes context for off-line scripts, including scheduled scripts. For
   * security reasons, this method should never be accessed by any code that
   * will get executed by an online (web) request
   */
  public Context(boolean offLine) {
    this();
    offline = offLine;
  }

  //------------------------------------------------------ Getters and Setters
  
  public boolean isUserLoggedIn() {
    return getUserId() != null;
  }

  public String getSessionId() {
    return getAttributeStr(SessionAttr.sessionId.toString());
  }

  public String getUserId() {
    return getAttributeStr(SessionAttr.userId.toString());
  }

  public void setUserId(String userId) {
    putAttribute(SessionAttr.userId.toString(), userId);
  }

  public String getOrgId() {
    return getAttributeStr(SessionAttr.orgId.toString());
  }

  public void setOrgId(String orgId) {
    putAttribute(SessionAttr.orgId.toString(), orgId);
  }

  public String getTenantId() {
    return getAttributeStr(SessionAttr.tenantId.toString());
  }

  public void setTenantId(String tenantId) {
    putAttribute(SessionAttr.tenantId.toString(), tenantId);
  }

  public String getAttributeStr(String key) {
    return (String) attrMap.get(key);
  }

  public Object getAttribute(String key) {
    return attrMap.get(key);
  }

  public void putAttribute(String key, String value) {
    log.info(key + ":" + value);
    for (SessionAttr attr : SessionAttr.values()) {
      if (key.equals(attr.toString())) {
        sessionUpdated = true;
        break;
      }
    }
    attrMap.put(key, value);
  }

  public boolean isOffline() {
    return offline;
  }

  public boolean isSessionUpdated() {
    return sessionUpdated;
  }

  // ---------------------------------------------------------- Static Methods

  /**
   * <p>
   * The <code>ThreadLocal</code> variable used to record the {@link Context}
   * instance for each processing thread.
   * </p>
   */
  private static ThreadLocal<Context> instance = new ThreadLocal<Context>() {
    protected Context initialValue() {
      return (null);
    }
  };

  /**
   * <p>
   * Returns the {@link Context} instance for the request that is being
   * processed by the current thread, if any.
   * </p>
   */
  public static Context getCurrentInstance() {
    return (instance.get());
  }

  /**
   * <p>
   * Sets the {@link Context} instance for the request that is being processed
   * by the current thread.
   * </p>
   * 
   * @param context
   *          The {@link Context} instance for the current thread, or
   *          <code>null</code> if this thread no longer has a
   *          <code>Context</code> instance.
   * 
   */
  private static void setCurrentInstance(Context context) {

    if (context == null) {
      instance.remove();
    } else {
      instance.set(context);
    }

  }

  public void setDaoContext(DaoContext daoContext) {
    this.daoContext = daoContext;
  }

  public DaoContext getDaoContext() {
    return daoContext;
  }

}
